﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UtilZ.Dotnet.Ex.Base;
using UtilZ.Dotnet.Ex.Cache;

namespace UtilZ.Dotnet.Ex.Json
{
    /// <summary>
    /// json序列化中心
    /// </summary>
    public partial class JsonSerializerCore
    {
        private static readonly Type _jsonIgnoreAttType = null;
        private static readonly Type _jsonPropertyAttType = null;

        static JsonSerializerCore()
        {
            _jsonIgnoreAttType = typeof(JsonIgnoreAttribute);
            _jsonPropertyAttType = typeof(JsonPropertyAttribute);
        }


        private readonly JsonSerializeSetting _setting;
        private readonly ObjectCache _jsonCache;
        private readonly TimeSpan _expire;
        private readonly BindingFlags _bindingFlags;
        private readonly Dictionary<Type, JsonNodeCollection> _childrenNodesDic = new Dictionary<Type, JsonNodeCollection>();

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="setting">序列化设置</param>
        /// <param name="jsonCache">缓存对象</param>
        /// <param name="expire">类型信息缓存时长</param>
        public JsonSerializerCore(JsonSerializeSetting setting, ObjectCache jsonCache, TimeSpan expire)
        {
            this._setting = setting;
            this._jsonCache = jsonCache;
            this._expire = expire;
            this._bindingFlags = BindingFlags.Public | BindingFlags.Instance;
        }


        private JsonNode GetJsonNode(Type type)
        {
            object key = $"{type.FullName}_{this._setting.GetHashCode()}";
            JsonNode jsonNode = this._jsonCache.Get(key) as JsonNode;
            if (jsonNode != null)
            {
                return jsonNode;
            }

            lock (this._childrenNodesDic)
            {
                jsonNode = this._jsonCache.Get(key) as JsonNode;
                if (jsonNode != null)
                {
                    return jsonNode;
                }

                jsonNode = this.BuildJsonNodeTree(type);
                this._childrenNodesDic.Clear();
                jsonNode.Sort();
                this._jsonCache.Set(key, jsonNode, this._expire);
                return jsonNode;
            }
        }


        private JsonNode BuildJsonNodeTree(Type type)
        {
            TypeCategorys typeCategorys = TypeEx.GetTypeTypeCategory(type);
            JsonNodeCollection childrenNodes = null, subChildrenNodes;
            Type genericParameterType;
            TypeCategorys genericParameterTypeCategory;
            IJsonValueConverter genericParameterConverter = null;

            switch (typeCategorys)
            {
                case TypeCategorys.Basic:
                case TypeCategorys.Any:
                    break;
                case TypeCategorys.Object:
                    childrenNodes = this.BuildObjectMemberInfoJsonNode(type);
                    break;
                case TypeCategorys.IList:
                    genericParameterType = TypeEx.GetIListElementType(type);
                    genericParameterTypeCategory = TypeEx.GetTypeTypeCategory(genericParameterType);

                    switch (genericParameterTypeCategory)
                    {
                        case TypeCategorys.Basic:
                        case TypeCategorys.Any:
                            genericParameterConverter = JsonConverterManger.GetDefaultConverter(genericParameterType);
                            subChildrenNodes = null;
                            break;
                        case TypeCategorys.Object:
                            subChildrenNodes = this.BuildObjectMemberInfoJsonNode(genericParameterType);
                            break;
                        case TypeCategorys.IList:
                        case TypeCategorys.IDictionary:
                            subChildrenNodes = this.BuildObjectJsonNode(genericParameterType);
                            break;
                        default:
                            throw new NotSupportedException(genericParameterTypeCategory.ToString());
                    }

                    var ilistNode = new JsonNode(genericParameterType, genericParameterTypeCategory, genericParameterConverter);
                    ilistNode.UpdateChildren(subChildrenNodes);

                    childrenNodes = new JsonNodeCollection();
                    childrenNodes.Add(ilistNode);
                    break;
                case TypeCategorys.IDictionary:
                    IDictionaryKeyValueType keyValueType = TypeEx.GetIDictionaryKeyValueType(type);
                    genericParameterType = keyValueType.ValueType;
                    genericParameterTypeCategory = TypeEx.GetTypeTypeCategory(genericParameterType);

                    switch (genericParameterTypeCategory)
                    {
                        case TypeCategorys.Basic:
                        case TypeCategorys.Any:
                            genericParameterConverter = JsonConverterManger.GetDefaultConverter(genericParameterType);
                            subChildrenNodes = null;
                            break;
                        case TypeCategorys.Object:
                            subChildrenNodes = this.BuildObjectMemberInfoJsonNode(genericParameterType);
                            break;
                        case TypeCategorys.IList:
                        case TypeCategorys.IDictionary:
                            subChildrenNodes = this.BuildObjectJsonNode(genericParameterType);
                            break;
                        default:
                            throw new NotSupportedException(genericParameterTypeCategory.ToString());
                    }

                    var iDicNode = new JsonNode(keyValueType.KeyType, genericParameterType, genericParameterTypeCategory, genericParameterConverter);
                    iDicNode.UpdateChildren(subChildrenNodes);

                    childrenNodes = new JsonNodeCollection();
                    childrenNodes.Add(iDicNode);
                    break;
                default:
                    throw new NotSupportedException($"不支持的序列化类型{type.FullName}");
            }

            var jsonNode = new JsonNode(type, typeCategorys, null);
            jsonNode.UpdateChildren(childrenNodes);
            return jsonNode;
        }


        private JsonNodeCollection BuildObjectMemberInfoJsonNode(Type type)
        {
            JsonNodeCollection nodes;
            if (this._childrenNodesDic.TryGetValue(type, out nodes))
            {
                return nodes;
            }

            nodes = new JsonNodeCollection();
            this._childrenNodesDic.Add(type, nodes);
            this.PrimitiveBuildObjectMemberInfoJsonNode(type, nodes);
            return nodes;
        }


        private void PrimitiveBuildObjectMemberInfoJsonNode(Type type, JsonNodeCollection nodes)
        {
            MemberInfo[] objMembers = this.GetTypePropertyAndFieldMembers(type);
            if (objMembers == null || objMembers.Length == 0)
            {
                return;
            }

            Attribute objAtt;
            JsonPropertyAttribute jsonPropertyAtt;
            JsonNode jsonNode;
            PropertyInfo propertyInfo;
            FieldInfo fieldInfo;
            Type memberType, nestedType;
            TypeCategorys memberTypeCategory;
            JsonNodeCollection childrenNodes, childrenNodes2;
            var ignoreMembers = this._setting.IgnoreMembers;

            foreach (var objMember in objMembers)
            {
                if (ignoreMembers != null &&
                    (ignoreMembers.Contains($"{objMember.DeclaringType.FullName}.{objMember.Name}") || ignoreMembers.Contains($"{objMember.DeclaringType.Name}.{objMember.Name}")))
                {
                    //忽略的成员
                    continue;
                }

                if (objMember.MemberType == MemberTypes.Property)
                {
                    propertyInfo = (PropertyInfo)objMember;
                    if (!propertyInfo.CanRead)
                    {
                        //忽略不可读属性
                        continue;
                    }

                    if (this._setting.IgnoreReadOnlyProperties && !propertyInfo.CanWrite)
                    {
                        //忽略只读属性
                        continue;
                    }
                    memberType = propertyInfo.PropertyType;
                }
                else if (objMember.MemberType == MemberTypes.Field)
                {
                    fieldInfo = (FieldInfo)objMember;
                    if (this._setting.IgnoreReadOnlyFields && fieldInfo.IsInitOnly)
                    {
                        //忽略只读字段
                        continue;
                    }
                    memberType = fieldInfo.FieldType;
                }
                else
                {
                    throw new NotSupportedException($"不支持的成员序列化类型:{objMember.MemberType.ToString()}");
                }

                objAtt = objMember.GetCustomAttribute(_jsonIgnoreAttType, true);
                if (objAtt != null)
                {
                    //该成员标记为忽略
                    continue;
                }

                objAtt = objMember.GetCustomAttribute(_jsonPropertyAttType, true);
                if (objAtt != null)
                {
                    jsonPropertyAtt = (JsonPropertyAttribute)objAtt;
                }
                else
                {
                    //未标记特性,创建一个属性特性
                    jsonPropertyAtt = JsonPropertyAttribute.Default;
                }


                memberTypeCategory = TypeEx.GetTypeTypeCategory(memberType);
                childrenNodes = null;

                switch (memberTypeCategory)
                {
                    case TypeCategorys.Basic:
                    case TypeCategorys.Any:
                        break;
                    case TypeCategorys.Object:
                        childrenNodes = this.BuildObjectMemberInfoJsonNode(memberType);
                        break;
                    case TypeCategorys.IList:
                        {
                            nestedType = TypeEx.GetIListElementType(memberType);
                            var nestedTypeCategory = TypeEx.GetTypeTypeCategory(nestedType);
                            if (nestedTypeCategory != TypeCategorys.Basic && nestedTypeCategory != TypeCategorys.Any)
                            {
                                childrenNodes2 = this.BuildObjectJsonNode(nestedType);
                                jsonNode = new JsonNode(nestedType, nestedTypeCategory, null);
                                jsonNode.UpdateChildren(childrenNodes2);
                                childrenNodes = new JsonNodeCollection() { jsonNode };
                            }
                            else
                            {
                                childrenNodes = new JsonNodeCollection()
                                    {
                                        new JsonNode(nestedType, nestedTypeCategory, JsonConverterManger.GetDefaultConverter(nestedType))
                                    };
                            }
                        }
                        break;
                    case TypeCategorys.IDictionary:
                        {
                            IDictionaryKeyValueType keyValueType = TypeEx.GetIDictionaryKeyValueType(memberType);
                            nestedType = keyValueType.ValueType;

                            var nestedTypeCategory = TypeEx.GetTypeTypeCategory(nestedType);
                            if (nestedTypeCategory != TypeCategorys.Basic && nestedTypeCategory != TypeCategorys.Any)
                            {
                                childrenNodes2 = this.BuildObjectJsonNode(nestedType);
                                jsonNode = new JsonNode(keyValueType.KeyType, nestedType, nestedTypeCategory, null);
                                jsonNode.UpdateChildren(childrenNodes2);
                                childrenNodes = new JsonNodeCollection() { jsonNode };
                            }
                            else
                            {
                                childrenNodes = new JsonNodeCollection()
                                    {
                                        new JsonNode(keyValueType.KeyType,nestedType, nestedTypeCategory, JsonConverterManger.GetDefaultConverter(nestedType))
                                    };
                            }
                        }
                        break;
                    default:
                        throw new NotSupportedException($"不支持的序列化类型{type.FullName}");
                }

                jsonNode = new JsonNode(jsonPropertyAtt, objMember, memberType, memberTypeCategory, this._setting.UseCamelCasePropertyNames);
                if (childrenNodes != null)
                {
                    jsonNode.UpdateChildren(childrenNodes);
                }
                nodes.Add(jsonNode);
            }
        }



        private JsonNodeCollection BuildObjectJsonNode(Type type)
        {
            JsonNodeCollection nodes;
            if (this._childrenNodesDic.TryGetValue(type, out nodes))
            {
                return nodes;
            }
            nodes = new JsonNodeCollection(); ;
            this._childrenNodesDic.Add(type, nodes);


            TypeCategorys typeCategory = TypeEx.GetTypeTypeCategory(type);
            Type nestedType;
            JsonNodeCollection childrenNodes;
            JsonNode jsonNode;

            switch (typeCategory)
            {
                case TypeCategorys.Basic:
                    jsonNode = new JsonNode(type, typeCategory, JsonConverterManger.GetDefaultConverter(type));
                    nodes.Add(jsonNode);
                    break;
                case TypeCategorys.Object:
                    this.PrimitiveBuildObjectMemberInfoJsonNode(type, nodes);
                    break;
                case TypeCategorys.IList:
                    {
                        nestedType = TypeEx.GetIListElementType(type);
                        var nestedTypeCategory = TypeEx.GetTypeTypeCategory(nestedType);
                        if (nestedTypeCategory != TypeCategorys.Basic)
                        {
                            childrenNodes = this.BuildObjectJsonNode(nestedType);
                            jsonNode = new JsonNode(nestedType, TypeCategorys.Object, null);
                            jsonNode.UpdateChildren(childrenNodes);
                        }
                        else
                        {
                            jsonNode = new JsonNode(nestedType, nestedTypeCategory, JsonConverterManger.GetDefaultConverter(nestedType));
                        }
                        nodes.Add(jsonNode);
                    }
                    break;
                case TypeCategorys.IDictionary:
                    {
                        IDictionaryKeyValueType keyValueType = TypeEx.GetIDictionaryKeyValueType(type);
                        nestedType = keyValueType.ValueType;

                        var nestedTypeCategory = TypeEx.GetTypeTypeCategory(nestedType);
                        if (nestedTypeCategory != TypeCategorys.Basic)
                        {
                            childrenNodes = this.BuildObjectJsonNode(nestedType);
                            jsonNode = new JsonNode(keyValueType.KeyType, nestedType, TypeCategorys.Object, null);
                            jsonNode.UpdateChildren(childrenNodes);
                        }
                        else
                        {
                            jsonNode = new JsonNode(keyValueType.KeyType, nestedType, nestedTypeCategory, JsonConverterManger.GetDefaultConverter(nestedType));
                        }
                        nodes.Add(jsonNode);
                    }
                    break;
                default:
                    throw new NotSupportedException($"不支持的序列化类型{type.FullName}");
            }

            return nodes;
        }

        private MemberInfo[] GetTypePropertyAndFieldMembers(Type type)
        {
            switch (this._setting.MemberType)
            {
                case MemberTypes.Property:
                    return type.GetProperties(this._bindingFlags);
                case MemberTypes.Field:
                    return type.GetFields(this._bindingFlags);
                case MemberTypes.All:
                    var propertyInfos = type.GetProperties(this._bindingFlags);
                    var fieldInfos = type.GetFields(this._bindingFlags);
                    var arr = new MemberInfo[propertyInfos.Length + fieldInfos.Length];
                    Array.Copy(propertyInfos, arr, propertyInfos.Length);
                    Array.Copy(fieldInfos, 0, arr, propertyInfos.Length, fieldInfos.Length);
                    return arr;
                default:
                    throw new NotSupportedException($"不支持的成员序列化类型:{this._setting.MemberType.ToString()}");
            }
        }
    }
}
